Skip to main content

New product creation

Business scenario

When a merchant adds a product to their integrated system — an e-commerce platform, a POS system, or any other catalog source — that product must also exist in Qoyod before it can appear on invoices, be tracked across inventory locations, or have its cost and revenue correctly posted to the chart of accounts.

This use case covers the integrated system to Qoyod direction: your integration detects that a new product has been created in the integrated system, then sends a request to Qoyod to create a matching record. The result is a Qoyod product with a numeric id that your integration stores and reuses for all future operations on that record.

If this step is skipped or fails silently, Qoyod has no record of the product. Subsequent invoice creation fails because it cannot reference a product that does not exist. Inventory tracking cannot start. Accounting entries for sales and cost of goods are not posted. The longer the gap goes unaddressed, the more remediation work is required to backfill the missing records.

When to use this

Use this when:

  • A new product is added to your integrated system and no corresponding Qoyod record yet exists for it.
  • You are creating a product for the first time — there is no prior Qoyod ID stored against this item.
  • You need to establish the product's type classification, which can only be set at creation time.

Do not use this when:

  • The product already exists in Qoyod and you need to change its name, price, or other fields — that is product update (UC-02).
  • You are migrating an existing catalog of many products at once — that is initial product import (UC-07), which covers batch creation considerations.
  • You need to set or adjust stock quantities — stock is managed through inventory adjustments, not on product creation.

Prerequisites

  • A valid OAuth 2.0 access token for the Qoyod account you are integrating with.
  • If you plan to assign the product to a category, the category must already exist in Qoyod. Retrieve its integer id from Qoyod before sending the product creation request. Category creation is out of scope for this page.
  • If you plan to associate a tax rate, the tax record must already exist in Qoyod. Retrieve its integer id before sending the request.
  • A reliable mechanism in your integration to capture and store the Qoyod product id returned in the 201 Created response. This ID is required for all future updates and references to this product.

Sequence diagram

Step-by-step

1. Build the request body

Wrap the product fields in a product object. At minimum, send en_name or name (the Arabic name) so the record is identifiable. Include type to classify the product correctly — the type field is ignored on update, so it must be set accurately here.

Request body — minimal product
{
"product": {
"en_name": "Blue Ceramic Mug",
"name": "كوب سيراميك أزرق",
"type": "Product",
"sku": "MUG-BLUE-001",
"selling_price": 25.00,
"buying_price": 12.00,
"track_quantity": 1,
"category_id": 42
}
}

The selling_price field, when present, causes Qoyod to set is_sold: true on the record automatically. The buying_price field, when present, causes Qoyod to set is_bought: true. You do not need to set these boolean fields explicitly.

2. Send the request

Send a POST request to https://api.qoyod.com/2.0/products with the Authorization header carrying your access token.

Send the request
curl -X POST https://api.qoyod.com/2.0/products \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"product": {
"en_name": "Blue Ceramic Mug",
"name": "كوب سيراميك أزرق",
"type": "Product",
"sku": "MUG-BLUE-001",
"selling_price": 25.00,
"buying_price": 12.00,
"track_quantity": 1,
"category_id": 42
}
}'

3. Handle the 201 Created response

A successful single-product creation returns 201 Created with the new product record nested under a product key. Extract and store the id field immediately — it is the Qoyod product ID your integration uses for every future operation on this record.

Response — 201 Created
{
"product": {
"id": 1087,
"name_ar": "كوب سيراميك أزرق",
"name_en": "Blue Ceramic Mug",
"type": "Product",
"sku": "MUG-BLUE-001",
"selling_price": 25.0,
"buying_price": 12.0,
"is_sold": true,
"is_bought": true,
"track_quantity": 1,
"category_id": 42,
"inventories": [],
"created_at": "2026-05-17T10:23:00.000Z"
}
}

Store the value of id — in this example, 1087 — on the corresponding record in your integrated system. The inventories array is empty at creation because no stock transactions have occurred yet.

After storing the Qoyod ID, retrieve the product to confirm the record is complete and that Qoyod interpreted all fields as intended. See the Verification section below.

Field mapping

The table below maps common integrated system concepts to the corresponding Qoyod fields for the product creation request. All field names match the POST /2.0/products request body exactly.

Integrated system conceptQoyod fieldRequiredNotes
Product name (English)en_nameRecommendedAlso accepted as name_en. Send in all cases where an English name exists.
Product name (Arabic)nameRecommendedAlso accepted as name_ar or ar_name. At least one name field should be sent.
Product type classificationtypeNo (defaults to Product)Must be set correctly at creation; ignored on update. See type selection guidance below.
SKUskuNoPass through from the integrated system.
Selling priceselling_priceNoNumeric. Automatically sets is_sold: true on the record.
Buying pricebuying_priceNoNumeric. Automatically sets is_bought: true on the record.
Inventory tracking enabledtrack_quantityNo0 = untracked (default). 1 = Qoyod manages stock levels for this product.
Categorycategory_idNoInteger. Must reference an existing Qoyod category ID.
Taxtax_idNoInteger. Must reference an existing Qoyod tax ID.

For the complete list of fields accepted by POST /2.0/products, see the products reference.

Type selection guidance. Choose type based on what the product record represents:

  • Product — a physical good that can be bought and sold. This is the default when type is omitted.
  • Service — an intangible offering that can be sold but does not carry stock.
  • Expense — a cost item that is not sold to customers.
  • RawMaterial — a component used as input in a Recipe.
  • Recipe — a compound product assembled from RawMaterial ingredients.

Verification

After a successful 201 Created response, retrieve the product using GET /2.0/products/{id} to confirm the record exists in Qoyod with the values you sent.

Retrieve the product
curl -X GET https://api.qoyod.com/2.0/products/{product_id} \
-H "Authorization: Bearer {access_token}"

A successful retrieval returns 200 OK with a product object. Confirm:

  • The id field is present and matches the value you stored.
  • name_en and name_ar match what you sent.
  • type reflects the value you set on creation.
  • track_quantity is 1 if you intend to track stock for this product.
  • sku matches what you sent, if provided.

If any field does not match your expectation, check whether the field name you used in the request body is a supported alias. For example, name_ar and ar_name are both accepted aliases for the Arabic name field on POST, but the response always returns the value as name_ar.

note

The inventories array in the response will be empty immediately after creation. Stock levels appear here only after stock-affecting transactions — invoices, bills, stock takes, or inventory transfers — have been recorded in Qoyod for this product.

Error scenarios

Status codeWhen it occursWhat to do
422 Unprocessable EntityQoyod could not create the record due to a validation failure. The response body contains an errors object where keys are field names and values are arrays of error strings.Read the errors object to identify which fields failed validation. Correct the field values in your request body and retry.
422 Unprocessable EntityThe category_id you sent does not reference an existing Qoyod category.Verify the category ID by querying the categories endpoint. Ensure the category was created before this product creation request.
401 UnauthorizedThe access token is missing, expired, or invalid.Refresh the OAuth 2.0 access token and retry the request.

For retry logic, exponential backoff guidance, and handling transient errors, see error handling.